// In Class API Program Entry Point

#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif

#include <iostream>
#include <cstdlib>

#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/gl.h>
#include <GL/glut.h>
#endif

#include <math.h>

using namespace std;

#include "Balls.h"
#include "Vec3f.h"
#include "Common.h"

const double PI = 3.14159265;

Ball::Ball() : hAngle(0), vAngle(0), latestHAngle(0), latestVAngle(0), pocketed(false), stopped(false)
{
   state.index = balls.size();
   balls.push_back(this);
   state.moving = false;
   state.onTable = true;  
   
   radius = Config::getBallDiameter() / 2;
   setOrigin(Vec3f(0,0,0));   
   
   setShininess(64);
   setSpecular(Vec3f(.8,.8,.8));
   setColor(Vec3f(1,0,0));      
}

void Ball::setOrigin(Vec3f _origin) {
   origin = _origin;
   if (force.getVelocity() == 0)
      initialPosition = _origin;
}

void Ball::inPocket()
{
   setOrigin(getPosition());
   startTime = Time::getTickCount();
   pocketed = true;
   state.moving = false;
   force = Force(force.getAngle(), 0);
}

double Ball::getCurrentHAngle()
{
   latestHAngle = force.getAngle() - 180;
   return latestHAngle;   
}

double Ball::getCurrentVAngle()
{
   Vec3f p = getPosition();
   double angle = force.getAngle();
   double x = p[0] - origin[0];
   double z= p[2] - origin[2];
   double distance = sqrt(x*x + z*z);
   double perimeter = 2 * radius * PI;
   latestVAngle = distance / perimeter * (-360.0); 
   return latestVAngle;
}

bool Ball::isStopped()
{
   return stopped;
}

void Ball::draw(bool ortho)
{
   if (stopped)
   {
      if (getTexture() == TEXTURE_CUEBALL)
      {
         hAngle = latestHAngle = 0;
         vAngle = latestVAngle = 0;
         pocketed = false;
         stopped = false;
         state.moving = false;
         force = Force();
         setOrigin(Vec3f(0, 0, Config::getTableDepth() / 4.0));
      }
      else
      {
         return;
      }
   }
   
   glPushMatrix();   
   glPushName(100 + count); /* Overwrite the first name in the buffer */
   
   Vec3f p = getPosition();
   
   IDrawableObject::draw(ortho);
   
   glPushMatrix();
   glTranslatef(p[0], p[1] + radius, p[2]);
   glScalef(radius, radius, radius);
   
//   if (getTexture() == TEXTURE_CUEBALL) {
//      cout << "Vertical: " << getCurrentVAngle() << ":" << vAngle << endl;
//      cout << "Horizontal: " << getCurrentHAngle() << ":" << hAngle << endl;
//   }
      
   glRotatef(getCurrentHAngle(), 0, 1, 0);
   glRotatef(getCurrentVAngle(), 1, 0, 0);
   glRotatef(getCurrentHAngle(), 0, 1, 0);
   
   
   glRotatef(vAngle, 1, 0, 0);
   glRotatef(hAngle, 0, 1, 0);
   glRotatef(-hAngle, 0, 1, 0);
   
   // Enable texturing and select first texture
   glColor3f(1.0f,1.0f,1.0f);
   
   // setup quadric
   GLUquadricObj *quadric = gluNewQuadric();
   gluQuadricNormals(quadric, GLU_SMOOTH);
   gluQuadricTexture(quadric, GL_TRUE);
   
   // Use a texture
   Config::getTextures()->useTexture(getTexture());
   gluSphere(quadric, 1.0f, 32, 32);   
   glDisable(GL_TEXTURE_2D); // Disable texturing
   
   glPopName();
   glPopMatrix();   

}

void Ball::drawShadow(Vec3f l, Vec3f dif, int lightNum)
{
   if (pocketed || stopped)
      return;
   
   IDrawableObject::draw(false);
   
   glPushMatrix();   
   glPushName(0); /* Overwrite the first name in the buffer */
   Vec3f p = getPosition();
   
   double xChange = p[0] - l[0];
   double yChange = l[1] - p[1] - radius;
   double xyTheta = atan(xChange / yChange);
   double xyThetaC = atan(radius / sqrt(xChange*xChange + yChange*yChange));
   double xLeft = l[0] + l[1] * tan(xyTheta - xyThetaC);
   double xRight = l[0] + l[1] * tan(xyTheta + xyThetaC);
   

   double zChange = p[2] - l[2];
   double zyTheta = atan(zChange / yChange);
   double zyThetaC = atan(radius / sqrt(zChange*zChange + yChange*yChange));
   double zFront = l[2] + l[1] * tan(zyTheta - zyThetaC);
   double zBack = l[2] + l[1] * tan(zyTheta + zyThetaC);
   
   double dRatio = sqrt(xChange*xChange + zChange*zChange) / (0.8 * Config::getTableDepth());

   if (dRatio >= 1)
      dRatio = 1;
   
   Config::getTextures()->useTexture(TEXTURE_SHADOW);
   
   glBegin(GL_QUADS);
   glColor4f(1.0f,1.0f,1.0f, 0.5 - 0.39 * dRatio);
   glNormal3f(+0.0f, +1.0f,+0.0f);
   glTexCoord2f(1.0f,1.0f); glVertex3f(xLeft, 1 + lightNum, zBack);
   glTexCoord2f(1.0f,0.0f); glVertex3f(xLeft, 1 + lightNum, zFront);
   glTexCoord2f(0.0f,0.0f); glVertex3f(xRight, 1 + lightNum, zFront);
   glTexCoord2f(0.0f,1.0f); glVertex3f(xRight, 1 + lightNum, zBack);
   
   glEnd();
   glDisable(GL_TEXTURE_2D); // Disable texturing
   
   glPopName();
   glPopMatrix();
}

double Ball::getRadius()
{
   return radius;
}

double Ball::getDiameter()
{
   return radius * 2;
}

Vec3f Ball::getOrigin() 
{
   return origin;
}

void Ball::setForce(Force _force)
{
   setOrigin(getPosition());
   force = _force;
   startTime = Time::getTickCount();
}

Force Ball::getForce()
{
   if (force.getVelocity() == 0)
      return force;

   long time = getTime();
   double friction = 0.5 * time * 0.002;
   if (time * 0.002 >= force.getVelocity())
   {
      Force f = Force(force.getAngle(), force.getVelocity() - friction);
      origin[0] += f[0] * time;
      origin[2] += f[2] * time;
      initialPosition = origin; // reset initial position
      hAngle = latestHAngle;
      vAngle = latestVAngle;

      force = Force();
      return force;
   }
   
   return Force(force.getAngle(), force.getVelocity() - friction);
}

Vec3f Ball::getPosition() 
{
   
   long time = getTime();
   
   if (pocketed)
   {
      double down =  -time*2;
      if (down < -3 * radius)
      {
         stopped = true; 
         pocketed = false;
         setOrigin(Vec3f(0,-2* radius, 0));
      }

      return Vec3f(origin[0], down, origin[2]);      
   }

   
   Force f = getForce();
   
   double xChange = f[0] * time;
   double zChange = f[2] * time;
   
   Vec3f p;
   p[0] = origin[0] + xChange;
   p[1] = origin[1];
   p[2] = origin[2] + zChange;
   
   //if (getTexture() == TEXTURE_CUEBALL)
   //   cout << xChange <<":"<< zChange <<":"<< f.getVelocity() << endl;
   
   if (f.getVelocity())
   {
      state.moving = true;
   }
   else
   {
      state.moving = false;
      
   }
     
   
   return p;
}

long Ball::getTime()
{
   long time = Time::getTickCount() - startTime;
   return time;
}

int Ball::getTexture()
{
   return TEXTURE_FIRST_BALL + state.index - 1; // ignore cueball (which is first)
}

Vec3f Ball::getInitialPosition()
{
   return initialPosition;
}

CueBall::CueBall() : Ball()
{   
   setShininess(10);
   setSpecular(Vec3f(.2,.2,.2));
   setColor(Vec3f(1,1,1));   
}

vector<Ball*> Ball::balls;


int CueBall::getTexture()
{
   return TEXTURE_CUEBALL;
}



